package net.softwarefabrik.bde.core.model.bnd;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import net.softwarefabrik.bde.core.model.BDEConstants;
import net.softwarefabrik.bde.core.model.BDEException;

/**
 * The configuration of a BDE project, stored in a .bnd file.
 * 
 * @author Matthias Kuespert
 */
public class BndConfiguration {

    /**
     * The <code>Properties</code> which hold the values of this
     * configuration.
     */
    private Properties                                 configuration = null;

    /**
     * The registered <code>BndConfigurationChangeListener</code>s.
     */
    private Collection<BndConfigurationChangeListener> listeners     = null;

    /**
     * Default constructor.
     */
    public BndConfiguration() {
        this.configuration = new Properties();
        this.listeners = new LinkedList<BndConfigurationChangeListener>();
    }

    /**
     * Initializing constructor.
     * 
     * @param fileName The file from which to read initial contents.
     * @throws BDEException If the file cannot be read or contains invalid data.
     */
    public BndConfiguration(String fileName) throws BDEException {
        this.configuration = new Properties();
        this.listeners = new LinkedList<BndConfigurationChangeListener>();
        try {
            load(new FileInputStream(fileName));
        } catch (IOException e) {
            throw new BDEException("Error loading Bnd configuration from '" + fileName + "':", e);
        }
    }

    /**
     * @return An array containing the existing headers.
     */
    public String[] getHeaders() {
        return this.configuration.keySet().toArray(new String[] {});
    }

    /**
     * Retrieves a value for the given header.
     * 
     * @param header The header to retrieve.
     * @return The value of the header.
     */
    public String getHeaderValue(String header) {
        String value = this.configuration.getProperty(header);
        if (value == null) {
            value = BDEConstants.EMPTY_STRING;
        }
        return value;
    }

    /**
     * Sets a value for the given header.
     * 
     * @param header The header to set.
     * @param value The value to set.
     */
    public void setHeaderValue(String header, String value) {
        if (!value.equals(this.configuration.getProperty(header))) {
        	if (value.equals("")) {
        		this.configuration.remove(header);
        	} else {
        		this.configuration.setProperty(header, value); 
        	}
            fireChangeEvent();
        }
    }

    /**
     * Sets a value for the given header.
     * 
     * @param header The header to set.
     * @param value The value to set,
     */
    public void setHeaderValue(String header, String[] value) {
        if (value == null) {
            return;
        }
        String data = "";
        boolean first = true;
        for (String entry : value) {
            if (!first) {
                data += ", ";
            } else {
                first = false;
            }
            data += entry;
        }
        //
        if (!data.equals(this.configuration.getProperty(header))) {
            this.configuration.setProperty(header, data);
            fireChangeEvent();
        }
    }

    /**
     * Loads the model content from the given <code>InputStream</code>.
     * 
     * @param input The stream to load from.
     * @throws BDEException if the stream could not be read.
     */
    public void load(InputStream input) throws BDEException {
        try {
            this.configuration.clear();
            this.configuration.load(input);
            fireChangeEvent();
        } catch (IOException e) {
            throw new BDEException("Could not load from InputStream.", e);
        }
    }

    /**
     * Stores the model content into the given <code>OutputStream</code>.
     * 
     * @param output The stream to store into.
     * @throws BDEException if the data could not be written.
     */
    public void store(OutputStream output) throws BDEException {
        try {
            this.configuration.store(output, null);
        } catch (FileNotFoundException e) {
            throw new BDEException("Could not open file.", e);
        } catch (IOException e) {
            throw new BDEException("Could not read from file.", e);
        }
    }

    /**
     * Prints this configuration to a String.
     * 
     * Note: uses Collections.sort() to enable byte comparison.
     * 
     * @return A <code>String</code> representation of this configuration.
     */
    @Override
    public String toString() {
        List<String> list = new LinkedList<String>();
        for (Object key : this.configuration.keySet().toArray()) {
            list.add((String) key + ": " + this.configuration.getProperty((String) key) + "\n");
        }
        Collections.sort(list);
        StringBuffer buffer = new StringBuffer();
        for (String entry : list) {
            buffer.append(entry);
        }
        return buffer.toString();
    }

    /**
     * Fires a <code>BndConfigurationChangeEvent</code>.
     */
    private void fireChangeEvent() {
        BndConfigurationChangeEvent event = new BndConfigurationChangeEvent();
        for (BndConfigurationChangeListener listener : this.listeners) {
            listener.configurationChanged(event);
        }
    }

    /**
     * Set a listener for <code>BndConfigurationChangeEvent</code>s.
     * 
     * @param listener The <code>BndConfigurationChangeEvent</code> to set.
     */
    public void addConfigurationChangeListener(BndConfigurationChangeListener listener) {
        if (!this.listeners.contains(listener)) {
            this.listeners.add(listener);
        }
    }

    /**
     * Remove a listener for <code>BndConfigurationChangeEvent</code>s.
     * 
     * @param listener The <code>BndConfigurationChangeEvent</code> to remove.
     */
    public void removeConfigurationChangeListener(BndConfigurationChangeListener listener) {
        if (this.listeners.contains(listener)) {
            this.listeners.remove(listener);
        }
    }
}
